#include <fstream>
#include <iostream>
#include <filesystem>
#include "gdal/ogr_geometry.h"
#include "gdal/gdal_priv.h"
#include "gdal/gdal_alg.h"
#include "json/json.h"
#include"../LibDGGSField/DGGSFieldLayer.hpp"
#include "../LibDGGSField/DGGSFieldBand.hpp"
#include "../LibDGGSField/DGGSFieldPartition.hpp"
using namespace NNU::DGGS::Field;

/*********************************************读写GeoTIFF****************************************************/
#pragma region  GeoTIFF读写声明
/// <summary>
///写入GeoTIFF文件
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="geoTransform">地理变换</param>
/// <param name="xSize">x轴大小</param>
/// <param name="ySize">y轴大小</param>
/// <param name="dataType">数据类型</param>
/// <param name="pDataList">数据列表</param>
/// <param name="noDataValue">无值</param>
/// <param name="bandCount">波段数</param>
/// <returns></returns>
template<class T>
bool saveToGeoTiff(const char* fileName, double* geoTransform, int xSize, int ySize, GDALDataType dataType, T** pDataList, double noDataValue, int bandCount);

/// <summary>
/// 读取GeoTIFF文件
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="geoTransform">地理变换</param>
/// <param name="xSize">x</param>
/// <param name="ySize">y</param>
/// <param name="dataType">数据类型</param>
/// <param name="pDataList">数据列表</param>
/// <param name="noDataValue">无值</param>
/// <param name="bandCount">波段数</param>
/// <returns></returns>
bool loadFromGeoTiff(const char* fileName, double* &geoTransform, int &xSize, int &ySize, GDALDataType &dataType, void** &pDataList, double &noDataValue, int &bandCount);

#pragma endregion
/*********************************************Raster2Grid***************************************************/
#pragma region  Raster2Grid声明
/// <summary>
/// 获取点对应的栅格值
/// </summary>
/// <pram name="coord">经纬度</param>
/// <param name="trans">地理变换参数</param>
/// <param name="xSize">栅格宽度</param>
/// <param name="ySize">栅格高度</param>
/// <param name="srcValueList">栅格数据</param>
/// <returns>栅格值</returns>
template<class T>
T getValueFromCoord(GeoCoord coord, double* trans, int xSize, int ySize, T* srcValueList);


/// <summary>
/// 从栅格中获取GridTIFF的属性值
/// </summary>
/// <param name="fieldBand">格网波段</param>
/// <param name="gridBBox">格网框</param>
/// <param name="GridTIFFValues">GridTIFF的属性值</param>
/// <returns></returns>
template<typename T>
bool setBandValues(DGGSFieldBand<T>*&fieldBand, DGGridBBox gridBBox, T *GridTIFFValues);


/// <summary>
/// 从栅格中获取GridTIFF的属性值
/// </summary>
/// <param name="srcValueList">栅格数据</param>
/// <param name="trans">地理变换参数</param>
/// <param name="xSize">栅格宽度</param>
/// <param name="ySize">栅格高度</param>
/// <param name="tagValueList">GridTIFF的属性值</param>
/// <param name="tagTrans">GridTIFF的变换参数</param>
/// <param name="gridSize">GridTIFF的栅格尺寸</param>
/// <param name="gridType">GridTIFF的栅格类型</param>
/// <returns></returns>
template<class T>
void setGridTIFFValueFromRaster(T* srcValueList, double* trans, int xSize, int ySize,T* tagValueList, GridTransform tagTrans, int gridSize,double noValue,DGGSGridType gridType);

/// <summary>
/// 设置栅格数据到栅格图层
/// </summary>
/// <param name="fieldLayer">栅格图层</param>
/// <param name="pDataList">栅格数据</param>
/// <param name="geoTransform">地理变换参数</param>
/// <param name="xSize">栅格宽度</param>
/// <param name="ySize">栅格高度</param>
/// <param name="noDataValue">栅格无效值</param>
template<class T>
void setValueForGridLayer(DGGSFieldLayer<T> &fieldLayer, void**pDataList, double*geoTransform, int xSize, int ySize, double noDataValue);

#pragma endregion
/*********************************************Grid2Raster***************************************************/
#pragma region  Grid2Raster声明

/// <summary>
/// 从json文件中中获取数据类型
/// </summary>
/// <param name="fieldJsonFile">json文件名</param>
/// <returns>数据类型</returns>
DGGSDataType getDataTypeFromJson(const char* fieldJsonFile);


/// <summary>
/// 从格网图层中获取栅格数据
/// </summary>
/// <param name="fieldLayer">格网图层</param>
/// <param name="pDataList">栅格数据</param>
/// <param name="geoTransform">地理变换参数</param>
/// <param name="xSize">栅格宽度</param>
/// <param name="ySize">栅格高度</param>
/// <param name="noDataValue">栅格无效值</param>
template<class T>
void getRasterValuesFromGridLayer(DGGSFieldLayer<T>&fieldLayer,T**&pDataList,double*&geoTransform,int &xSize, int &ySize,double &noDataValue);

/// <summary>
/// 保存栅格图层到栅格图层
/// </summary>
/// <param name="fieldLayer">格网图层</param>
/// <param name="tagFileName">栅格图层文件名</param>
/// <returns></returns>
template<class T>
void saveRasterFromGridLayer(DGGSFieldLayer<T>&fieldLayer,const char* tagFileName);

#pragma endregion
/***********************************************************************************************************/
#pragma region   辅助函数

GridTransform getGridTransFromGridBox(DGGridBBox gridBBox,int level)
{
    GridTransform gridTrans{};
    gridTrans.level=level;
    gridTrans.rowOrigin=gridBBox.rowMin;
    gridTrans.colOrigin=gridBBox.colMax+1-gridBBox.colSize;
    gridTrans.basePartition=gridBBox.basePartition;

    return gridTrans;
}

DGGridBBox getGridBoxFromGridTrans(GridTransform gridTrans,int gridSize)
{
    DGGridBBox gridBBox;
    gridBBox.basePartition=gridTrans.basePartition;
    gridBBox.colSize=gridSize;
    gridBBox.rowSize=gridSize;
    gridBBox.rowMin=gridTrans.rowOrigin;
    gridBBox.colMax=gridTrans.colOrigin+gridBBox.colSize-1;

    return gridBBox;
}

#pragma endregion
/***********************************************************************************************************/
#pragma region GeoTIFF读写实现

template<class T>
bool saveToGeoTiff(const char* fileName, double* geoTransform, int xSize, int ySize, GDALDataType dataType, T** pDataList, double noDataValue, int bandCount)
{
    GDALAllRegister();
    const char* pszFormat = "GTiff";
    GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
    if (poDriver == NULL)
    {
        return false;
    }
    char** papszOptions = NULL;
    GDALDataset* poDstDS = poDriver->Create(fileName, xSize, ySize, bandCount, dataType, papszOptions);
    poDstDS->SetGeoTransform(geoTransform);
    // 设置投影为WGS84
    OGRSpatialReference srs;
    srs.SetWellKnownGeogCS("WGS84");
    char* wkt = nullptr;
    srs.exportToWkt(&wkt);
    poDstDS->SetProjection(wkt);
    CPLFree(wkt);

    for (int i = 0; i < bandCount; i++)
    {
        poDstDS->GetRasterBand(i+1)->SetNoDataValue(noDataValue);
        poDstDS->GetRasterBand(i+1)->RasterIO(GF_Write, 0, 0, xSize, ySize, pDataList[i], xSize, ySize, dataType, 0, 0);
    }
    GDALClose((GDALDatasetH)poDstDS);

    return true;
}

bool loadFromGeoTiff(const char* fileName, double* &geoTransform, int &xSize, int &ySize, GDALDataType &dataType, void** &pDataList, double &noDataValue, int &bandCount)
{
    GDALAllRegister();

    //注册所有的驱动
    CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
    GDALAllRegister();

    //GDAL的driver
    auto poSrcDS = (GDALDataset*)GDALOpen(fileName, GA_ReadOnly);
    if (poSrcDS == NULL)
        return false;

    geoTransform= new double[6];
    poSrcDS->GetGeoTransform(geoTransform);

    xSize = poSrcDS->GetRasterXSize();
    ySize = poSrcDS->GetRasterYSize();

    dataType = poSrcDS->GetRasterBand(1)->GetRasterDataType();

    bandCount = poSrcDS->GetRasterCount();
    pDataList=new void*[bandCount];

    for (int bandId = 0; bandId < bandCount; bandId++)
    {
        // 分配数据空间
        size_t dataSize = GDALGetDataTypeSizeBytes(dataType) * xSize * ySize;
        pDataList[bandId]=CPLMalloc(dataSize);
        noDataValue=poSrcDS->GetRasterBand(bandId + 1)->GetNoDataValue();
        poSrcDS->GetRasterBand(bandId + 1)->RasterIO(GF_Read, 0, 0, xSize, ySize, pDataList[bandId],  xSize, ySize, dataType, 0, 0);
    }
    return true;
}

#pragma endregion
/*********************************************Raster2Grid***************************************************/
#pragma region Raster2Grid实现
template<class T>
T getValueFromCoord(GeoCoord coord, double* trans, int xSize, int ySize, T* srcValueList)
{
    double lon=coord.lon;
    double lat=coord.lat;

    double temp = trans[1] * trans[5] - trans[2] * trans[4];
    double tempX = 0.0;
    double tempY = 0.0;
    tempX = (trans[5] * (lon - trans[0]) - trans[2] * (lat - trans[3])) / temp;
    tempY = (trans[1] * (lat - trans[3]) - trans[4] * (lon - trans[0])) / temp;

    //会把插值点的大致行列号拟合为所在格元左下角
    int x = static_cast<int>(tempX);
    int y = static_cast<int>(tempY);

    if (lon < trans[0] + trans[1] * xSize && x == xSize)
        x--;
    if (lat > trans[3] + trans[5] * ySize && y == ySize)
        y--;

    T pixValue;
    if (x >= xSize || y >= ySize || x < 0 || y < 0)
        pixValue = noData;
    else
        pixValue = srcValueList[(int)(y * xSize + x)];
    return pixValue;
}

template<typename T>
bool setBandValues(DGGSFieldBand<T>*&fieldBand, DGGridBBox gridBBox, T *GridTIFFValues)
{
    DGGSElementType elementType=fieldBand->getElementType();
    int level=fieldBand->getLevel();

    //初始化数组
    int xSize=(int) gridBBox.rowSize*2;
    int ySize=(int) gridBBox.colSize*2;

    //获取该基菱形的数据
    long long int rowMax=gridBBox.rowSize+gridBBox.rowMin-1;
    long long int colMin=gridBBox.colMax-gridBBox.colSize+1;
    for(long long int row=gridBBox.rowMin;row<=rowMax;row++)
    {
        for(long long int col=colMin;col<=gridBBox.colMax;col++)
        {
            DGGSElementCode code;
            code.level=level;
            code.basePartition=gridBBox.basePartition;
            code.elementType=elementType;
            code.row=row;
            code.col=col;

            int x,y;
            fieldBand->code2XY(code,x,y,gridBBox);
            T pixValue=GridTIFFValues[(static_cast<long long>(ySize) - 2 - y) * xSize + x];
            if(pixValue==fieldBand->getNoDataValue())
                pixValue=(T)noData;
            fieldBand->setData(code, pixValue);
        }
    }
    return true;
}

template<class T>
void setGridTIFFValueFromRaster(T* srcValueList, double* trans, int xSize, int ySize,T* tagValueList, GridTransform tagTrans, int gridSize,double noValue,DGGSGridType gridType)
{
    IDGGSKernel* kernel= CreateKernelObj(gridType);
    DGGSFieldBand<T> band;
    for(int row=tagTrans.rowOrigin;row<tagTrans.rowOrigin+gridSize;row++)
    {
        for(int col=tagTrans.colOrigin;col<tagTrans.colOrigin+gridSize;col++)
        {
            GeoCoord coord;
            DGGSElementCode code;
            code.level=tagTrans.level;
            code.basePartition=tagTrans.basePartition;
            code.row=row;
            code.col=col;
            kernel->decode(code,coord);

            T pixValue= getValueFromCoord(coord, trans, xSize, ySize, srcValueList);
            int x, y;

            DGGridBBox gridBBox= getGridBoxFromGridTrans(tagTrans,gridSize);
            band.code2XY(code, x,  y, gridBBox);

            if(pixValue==noValue)
                pixValue=T(noData);

            tagValueList[(gridSize * 2 - 2 - y) * (gridSize * 2) + x] = pixValue;
            tagValueList[(gridSize * 2 - 2 - y) * (gridSize * 2) + x + 1] = pixValue;

        }
    }

}

template<class T>
void setValueForGridLayer(DGGSFieldLayer<T> &fieldLayer, void**pDataList, double*geoTransform, int xSize, int ySize, double noDataValue)
{
    int bandCount=fieldLayer.getbandCount();
    DGGSGridType gridType=fieldLayer.getGridType();
    int level=fieldLayer.getLevel();

    for(int bandId=0;bandId<bandCount;bandId++)
    {
        DGGSFieldBand<T>* dggsFieldBand=fieldLayer.getBand(bandId);
        T *srcValueList = static_cast<T *>(pDataList[bandId]);

        for (int basePartition = 1; basePartition <= baseDiamondCount; basePartition++)
        {
            DGGridBBox gridBBox;
            if (!dggsFieldBand->getGridBoxFromPartitionIndex(gridBBox, basePartition))
                continue;
            T *tagValueList = new T[gridBBox.colSize * gridBBox.rowSize * 4];
            GridTransform tagTrans = getGridTransFromGridBox(gridBBox, level);
            setGridTIFFValueFromRaster(srcValueList, geoTransform, xSize, ySize, tagValueList, tagTrans,
                                       gridBBox.rowSize, noDataValue, gridType);

            setBandValues(dggsFieldBand, gridBBox, tagValueList);
            delete[]tagValueList;
        }
    }

}

void GDALRaster2DGGSField(const char* gdalFieldFileName, const char* dggsFieldFilePath, DGGSGridType gridType,DGGSElementType elementType,int level)
{
    //raster数据
    double* geoTransform=new double[6];
    int xSize, ySize;
    GDALDataType dataType;
    double noDataValue;
    int bandCount;
    void** pDataList= nullptr;
    loadFromGeoTiff(gdalFieldFileName, geoTransform, xSize, ySize, dataType, pDataList, noDataValue, bandCount);

    if(level==0)
        level=calculateLevel(geoTransform[1]);
    auto bound=new double[4];
    bound[2]=geoTransform[0];
    bound[3]=geoTransform[0]+xSize*geoTransform[1];
    bound[0]=geoTransform[3];
    bound[1]=geoTransform[3]+ySize*geoTransform[5];

    auto dggsDataType=static_cast<DGGSDataType>(dataType);

    std::filesystem::path path(gdalFieldFileName);
    std::string fileName = path.stem().string();
    std::string fieldJsonFile= std::string(dggsFieldFilePath) + "/" + fileName + ".json";

#define GDAL_HANDLE_DATA_TYPE(T,D) \
        case D:                        \
        {                           \
            DGGSFieldLayer<T> fieldLayer(bandCount,dggsDataType,elementType,gridType,level,bound);\
            setValueForGridLayer(fieldLayer,pDataList,geoTransform, xSize,  ySize, noDataValue);  \
            fieldLayer.saveToFile(dggsFieldFilePath);\
            break;                                  \
        }


    switch (dataType)
    {
        GDAL_HANDLE_DATA_TYPE(unsigned char, GDT_Byte)
        GDAL_HANDLE_DATA_TYPE(unsigned short, GDT_UInt16)
        GDAL_HANDLE_DATA_TYPE(short, GDT_Int16)
        GDAL_HANDLE_DATA_TYPE(unsigned int, GDT_UInt32)
        GDAL_HANDLE_DATA_TYPE(int, GDT_Int32)
        GDAL_HANDLE_DATA_TYPE(float, GDT_Float32)
        GDAL_HANDLE_DATA_TYPE(double, GDT_Float64)
        default:
            break;
    }

}

#pragma endregion
/*********************************************Grid2Raster***************************************************/
#pragma region Grid2Raster实现
DGGSDataType getDataTypeFromJson(const char* fieldJsonFile)
{
    std::ifstream jsonFile(fieldJsonFile);
    if (!jsonFile.is_open()) {
        std::cerr << "Failed to open file." << std::endl;
        return DGGSDataType ::DGGS_Unknown;
    }
    // 创建 Json::Value 对象来保存解析后的 JSON 数据
    Json::Value root;
    Json::CharReaderBuilder builder;
    std::string errs;
    if (!parseFromStream(builder, jsonFile, &root, &errs)) {
        std::cerr << "Failed to parse JSON: " << errs << std::endl;
        return DGGSDataType ::DGGS_Unknown ;
    }

    //读取json
    auto dataType= getDataTypeFromStr(root["dataType"].asCString());
    root.clear();
    jsonFile.close();

    return dataType;
}

template<class T>
void getRasterValuesFromGridLayer(DGGSFieldLayer<T>&fieldLayer,T**&pDataList,double*&geoTransform,int &xSize, int &ySize,double &noDataValue)
{
    int bandCount=fieldLayer.getbandCount();
    pDataList=new T*[bandCount];
    auto bound=new double[4];
    fieldLayer.getBound(bound);

    int level=fieldLayer.getLevel();

    double differ=calculateDiffer(level);
    geoTransform=new double[6];
    geoTransform[0]=bound[2];
    geoTransform[3]=bound[0];
    geoTransform[2]=0;
    geoTransform[4]=0;
    geoTransform[1]=differ;
    geoTransform[5]=-differ;

    xSize = (int)ceil((bound[2] - bound[3]) / -differ);
    ySize = (int)ceil((bound[0] - bound[1]) / differ);

    IDGGSKernel*pkernel= CreateKernelObj(fieldLayer.getGridType());
    for(int bandId=0;bandId<bandCount;bandId++)
    {
        DGGSFieldBand<T>* dggsFieldBand=fieldLayer.getBand(bandId);
        pDataList[bandId]=new T[xSize*ySize];
        for(int y=0;y<ySize;y++)
        {
            for(int x=0;x<xSize;x++)
            {
                double lon = geoTransform[0] + geoTransform[1] * (x+0.5) + geoTransform[2] * (y+0.5);
                double lat = geoTransform[3] + geoTransform[4] * (x+0.5) + geoTransform[5] * (y+0.5);

                GeoCoord coords{lon, lat};

                DGGSElementCode code;
                code.elementType=dggsFieldBand->getElementType();
                code.level=dggsFieldBand->getLevel();
                pkernel->encode(coords,code);

                T pixValue=dggsFieldBand->getData(code);
                pDataList[bandId][xSize*y+x]=pixValue;
            }
        }
    }

    noDataValue=fieldLayer.getBand(0)->getNoDataValue();
}

template<class T>
void saveRasterFromGridLayer(DGGSFieldLayer<T>&fieldLayer,const char* tagFileName)
{
    double* geoTransform;
    int xSize;
    int ySize;
    auto dataType=static_cast<GDALDataType>(fieldLayer.getDataType());
    T** pDataList= nullptr;
    double noDataValue=fieldLayer.getBand(0)->getNoDataValue();
    int bandCount = fieldLayer.getbandCount();
    getRasterValuesFromGridLayer(fieldLayer, pDataList, geoTransform, xSize, ySize, noDataValue);
    saveToGeoTiff(tagFileName, geoTransform, xSize, ySize, dataType, pDataList, noDataValue, bandCount);
    for(int i=0;i<bandCount;i++)
        delete[] pDataList[i];
    delete[] pDataList;
}

std::string getFilename(const char* file_path) {
    // 检查输入是否为空
    if (file_path == nullptr) {
        return "";
    }

    std::string file_name(file_path);
    // 查找最后一个路径分隔符
    std::string::size_type last_slash_pos = file_name.find_last_of("/\\");
    if (last_slash_pos != std::string::npos) {
        // 移除路径，只保留文件名
        file_name.erase(0, last_slash_pos + 1);
    }

    // 移除".tiff"或".tif"后缀
    if (file_name.length() > 5 && file_name.substr(file_name.length() - 5) == ".tiff") {
        file_name.erase(file_name.length() - 5);
    } else if (file_name.length() > 4 && file_name.substr(file_name.length() - 4) == ".tif") {
        file_name.erase(file_name.length() - 4);
    }

    return file_name;
}

// 检查文件是否有".tiff"或".tif"后缀
bool isTiffFile(const char* file_path) {
    // 检查输入是否为空
    if (file_path == nullptr) {
        return false;
    }

    std::string file_name(file_path);
    // 查找最后一个路径分隔符
    std::string::size_type last_slash_pos = file_name.find_last_of("/\\");
    if (last_slash_pos != std::string::npos) {
        file_name.erase(0, last_slash_pos + 1);
    }

    // 检查后缀是否为".tiff"或".tif"
    if (file_name.length() > 5 && file_name.substr(file_name.length() - 5) == ".tiff") {
        return true;
    } else if (file_name.length() > 4 && file_name.substr(file_name.length() - 4) == ".tif") {
        return true;
    }
    return false;
}

void singleDGGSField2GDALRaster(const char* srcFilePath,const char* tagFilePath)
{
    std::string tagFileName=std::string(tagFilePath) + std::string("/") + getFilename(srcFilePath)+std::string(".tiff");

    DGGSDataType dataType;
    {
        GridTIFF gridTiff;
        gridTiff.loadFromFile(srcFilePath);

        dataType = gridTiff.getDataType();
    }

#define SINGLE_DGGS_HANDLE_DATA_TYPE(T,D) \
        case D:                        \
        {                           \
            DGGSFieldLayer<T> fieldLayer;\
            fieldLayer.loadFromFile(srcFilePath);\
            saveRasterFromGridLayer(fieldLayer,tagFileName.c_str());\
            break;\
        }

    switch (dataType)
    {
        SINGLE_DGGS_HANDLE_DATA_TYPE(unsigned char, DGGS_Byte)
        SINGLE_DGGS_HANDLE_DATA_TYPE(unsigned short, DGGS_UInt16)
        SINGLE_DGGS_HANDLE_DATA_TYPE(short, DGGS_Int16)
        SINGLE_DGGS_HANDLE_DATA_TYPE(unsigned int, DGGS_UInt32)
        SINGLE_DGGS_HANDLE_DATA_TYPE(int, DGGS_Int32)
        SINGLE_DGGS_HANDLE_DATA_TYPE(float, DGGS_Float32)
        SINGLE_DGGS_HANDLE_DATA_TYPE(double, DGGS_Float64)
        default:
            break;
    }
}

void multDGGSField2GDALRaster(const char* srcFilePath,const char* tagFilePath)
{
    //获取文件夹下的json文件
    std::string jsonFileName;
    for (const auto& entry : std::filesystem::directory_iterator(srcFilePath))
    {
        // 检查是否为文件且扩展名为 .json
        if (entry.is_regular_file() && entry.path().extension() == ".json") {
            jsonFileName = entry.path().string();
            break;
        }
    }
    std::filesystem::path path(jsonFileName);
    std::string tagFileName =std::string(tagFilePath)+std::string("/")+ path.stem().string()+std::string(".tiff");


    DGGSDataType dataType=getDataTypeFromJson(jsonFileName.c_str());
    if (dataType==DGGSDataType::DGGS_Unknown)
        return;

#define DGGS_HANDLE_DATA_TYPE(T,D) \
        case D:                        \
        {                           \
            DGGSFieldLayer<T> fieldLayer;\
            fieldLayer.loadFromFile(jsonFileName.c_str());\
            saveRasterFromGridLayer(fieldLayer,tagFileName.c_str());\
            break;\
        }

    switch (dataType)
    {
        DGGS_HANDLE_DATA_TYPE(unsigned char, DGGS_Byte)
        DGGS_HANDLE_DATA_TYPE(unsigned short, DGGS_UInt16)
        DGGS_HANDLE_DATA_TYPE(short, DGGS_Int16)
        DGGS_HANDLE_DATA_TYPE(unsigned int, DGGS_UInt32)
        DGGS_HANDLE_DATA_TYPE(int, DGGS_Int32)
        DGGS_HANDLE_DATA_TYPE(float, DGGS_Float32)
        DGGS_HANDLE_DATA_TYPE(double, DGGS_Float64)
        default:
            break;
    }

}


void DGGSField2GDALRaster(const char* srcFilePath,const char* tagFilePath)
{

    if(isTiffFile(srcFilePath))
    {
        singleDGGSField2GDALRaster(srcFilePath,tagFilePath);
    }
    else
        multDGGSField2GDALRaster(srcFilePath,tagFilePath);
}

#pragma endregion

